@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
@@ -18,7 +18,7 @@ import { LinkHandler } from './strategies/link';
18
18
  import { AuthModal } from './ui/modal';
19
19
  export class PhoneAuthClient {
20
20
  constructor(config = {}) {
21
- var _a, _b, _c;
21
+ var _a, _b, _c, _d;
22
22
  this.crossDeviceActive = false;
23
23
  this.retryCount = 0;
24
24
  this.sessionCache = new Map();
@@ -28,7 +28,8 @@ export class PhoneAuthClient {
28
28
  this.config = {
29
29
  endpoints: {
30
30
  prepare: ((_a = config.endpoints) === null || _a === void 0 ? void 0 : _a.prepare) || '/api/magic-auth/prepare',
31
- process: ((_b = config.endpoints) === null || _b === void 0 ? void 0 : _b.process) || '/api/magic-auth/process'
31
+ process: ((_b = config.endpoints) === null || _b === void 0 ? void 0 : _b.process) || '/api/magic-auth/process',
32
+ polling: (_c = config.endpoints) === null || _c === void 0 ? void 0 : _c.polling // Pass through the polling endpoint
32
33
  },
33
34
  timeout: config.timeout || 30000,
34
35
  pollingInterval: config.pollingInterval || 2000, // Default 2 seconds
@@ -51,7 +52,7 @@ export class PhoneAuthClient {
51
52
  custom: config.logger
52
53
  });
53
54
  // Initialize developer tools if configured
54
- if (((_c = config.devtools) === null || _c === void 0 ? void 0 : _c.showMobileConsole) && typeof window !== 'undefined') {
55
+ if (((_d = config.devtools) === null || _d === void 0 ? void 0 : _d.showMobileConsole) && typeof window !== 'undefined') {
55
56
  import('./ui/mobile-debug-console').then(({ MobileDebugConsole }) => {
56
57
  MobileDebugConsole.init();
57
58
  console.log('[PhoneAuth] Mobile debug console enabled');
@@ -451,23 +452,25 @@ export class PhoneAuthClient {
451
452
  * });
452
453
  * ```
453
454
  *
454
- * @example Headless Mode (returns data)
455
+ * @example Extended Mode (returns control methods)
455
456
  * ```typescript
456
- * // Get raw data without showing UI
457
+ * // Get control methods for custom implementation
457
458
  * const result = await phoneAuth.invokeSecurePrompt(prepareResult, {
458
- * headless: true
459
+ * executionMode: 'extended',
460
+ * preventDefaultUI: true // Desktop: no modal
459
461
  * });
460
462
  *
461
- * if (result.strategy === 'link') {
462
- * // Open URL yourself
463
- * window.open(result.url);
464
- * await result.pollingPromise; // Wait for completion
463
+ * if (result.strategy === 'desktop') {
464
+ * // Show custom QR UI
465
+ * showCustomQR(result.qr_code_data);
466
+ * // Start polling
467
+ * await result.start_polling();
465
468
  * }
466
469
  * ```
467
470
  *
468
471
  * @param prepareResponse - Response from prepare() with strategy and data
469
- * @param options - Control UI behavior or enable headless mode
470
- * @returns Credential or HeadlessResult based on mode
472
+ * @param options - Control UI behavior and response type
473
+ * @returns Credential or ExtendedResponse based on executionMode
471
474
  */
472
475
  invokeSecurePrompt(prepareResponse, options) {
473
476
  return __awaiter(this, void 0, void 0, function* () {
@@ -475,7 +478,7 @@ export class PhoneAuthClient {
475
478
  // This ensures we work with plain objects for browser APIs
476
479
  // Vue's reactivity system wraps objects in Proxies which can interfere
477
480
  // with browser APIs like Digital Credentials API that expect plain objects
478
- var _a, _b, _c;
481
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
479
482
  // Try structuredClone first (modern browsers), but catch errors and fallback to JSON method
480
483
  let plainResponse;
481
484
  try {
@@ -495,16 +498,17 @@ export class PhoneAuthClient {
495
498
  console.log('[PhoneAuth] Session cache size:', this.sessionCache.size);
496
499
  console.log('[PhoneAuth] Retry count:', this.retryCount);
497
500
  console.log('[PhoneAuth] PrepareResponse received:', JSON.stringify(plainResponse, null, 2));
498
- // Check if we're in headless mode (new InvokeOptions format)
501
+ // Check if we're using new InvokeOptions format
499
502
  // Properly detect InvokeOptions by checking for any of its properties
500
- const invokeOptions = options && ('headless' in options ||
503
+ const invokeOptions = options && ('preventDefaultUI' in options ||
504
+ 'executionMode' in options ||
501
505
  'theme' in options ||
502
506
  'modalOptions' in options ||
503
507
  'callbacks' in options) ? options : undefined;
504
- // Smart defaults: Link and TS43 default to headless, Desktop defaults to UI mode
508
+ // Get configuration from options
505
509
  const strategy = plainResponse.authentication_strategy;
506
- const defaultHeadless = strategy === 'link' || strategy === 'ts43' ? true : false;
507
- const isHeadless = (_a = invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.headless) !== null && _a !== void 0 ? _a : defaultHeadless;
510
+ const preventDefaultUI = (_a = invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.preventDefaultUI) !== null && _a !== void 0 ? _a : false;
511
+ const executionMode = (_b = invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.executionMode) !== null && _b !== void 0 ? _b : 'standard';
508
512
  // DesktopAuthOptions is only used if not InvokeOptions
509
513
  const desktopOptions = options && !invokeOptions ? options : undefined;
510
514
  // Handle based on authentication strategy
@@ -643,270 +647,281 @@ export class PhoneAuthClient {
643
647
  throw error;
644
648
  }
645
649
  });
646
- // Headless mode for TS43 still returns the trigger function for custom UI
647
- // This allows developers to control WHEN to trigger, but not HOW
648
- if (isHeadless) {
649
- // Auto-trigger for TS43 if enabled (may fail due to browser restrictions)
650
- if ((invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.autoTrigger) !== false) {
651
- try {
652
- // Try to trigger immediately if we have user gesture context
653
- const result = yield enhancedTriggerTS43();
654
- // If successful, return the result directly
655
- return result;
656
- }
657
- catch (error) {
658
- // If auto-trigger fails, continue to return HeadlessResult with trigger
659
- if (this.debug) {
660
- console.log('[TS43] Auto-trigger failed, returning trigger function:', error);
661
- }
662
- }
663
- }
664
- // Return trigger function for developers who want to control timing
650
+ // TS43 always auto-triggers (no SDK UI ever)
651
+ // The Digital Credentials API provides its own OS-level UI
652
+ let credentialPromise;
653
+ try {
654
+ // Always try to trigger immediately
655
+ const vpToken = yield enhancedTriggerTS43();
656
+ // Convert to AuthCredential format
657
+ credentialPromise = Promise.resolve({
658
+ credential: typeof vpToken === 'string' ? vpToken : Object.values(vpToken)[0],
659
+ session: plainResponse.session,
660
+ authenticated: true
661
+ });
662
+ }
663
+ catch (error) {
664
+ // If auto-trigger fails, create a rejected promise
665
+ credentialPromise = Promise.reject(error);
666
+ }
667
+ // Handle based on execution mode
668
+ if (executionMode === 'extended') {
669
+ // Extended mode - return control methods
665
670
  return {
666
- strategy: API.AUTHENTICATION_STRATEGY.TS43,
667
- credentialRequest: secureCredentialRequest,
668
- trigger: enhancedTriggerTS43,
669
- session: plainResponse.session
671
+ strategy: 'ts43',
672
+ session: plainResponse.session,
673
+ credential: credentialPromise,
674
+ // Re-trigger credential request
675
+ trigger: () => __awaiter(this, void 0, void 0, function* () {
676
+ const vpToken = yield enhancedTriggerTS43();
677
+ // Update the credential promise
678
+ credentialPromise = Promise.resolve({
679
+ credential: typeof vpToken === 'string' ? vpToken : Object.values(vpToken)[0],
680
+ session: plainResponse.session,
681
+ authenticated: true
682
+ });
683
+ }),
684
+ cancel: () => {
685
+ // TS43 doesn't have a way to cancel once triggered
686
+ // but we can reject the promise
687
+ credentialPromise = Promise.reject(this.createError(PhoneAuthErrorCode.USER_DENIED, 'Authentication cancelled'));
688
+ }
670
689
  };
671
690
  }
672
691
  else {
673
- // In UI mode, directly invoke the Digital Credentials API
674
- // No modal needed - the OS provides its own UI
675
- return yield enhancedTriggerTS43();
692
+ // Standard mode - just return credential
693
+ // Wait for and return the credential
694
+ const credential = yield credentialPromise;
695
+ // Return in standard format
696
+ return {
697
+ [plainResponse.session.session_key]: credential.credential
698
+ };
676
699
  }
677
700
  }
678
701
  else if (plainResponse.authentication_strategy === API.AUTHENTICATION_STRATEGY.DESKTOP) {
679
702
  // Desktop strategy - QR code based authentication
680
703
  const desktopData = plainResponse.data;
681
- // Check if headless mode is requested
682
- if (isHeadless) {
683
- // Return raw data for custom UI implementation
684
- const handler = new DesktopHandler();
685
- // Start polling in the background
686
- const pollingPromise = handler.invoke(plainResponse, Object.assign(Object.assign({}, desktopOptions), {
687
- // Don't pass config.endpoints.polling - let backend-provided status_url take priority
688
- pollingInterval: this.config.pollingInterval || 2000, maxPollingAttempts: this.config.maxPollingAttempts || 150,
689
- // No UI callbacks in headless mode
690
- onQRCodeReady: undefined, onStatusUpdate: undefined })).then(result => {
691
- if (result.authenticated && result.credential) {
692
- // Extract session ID from nested or flat structure
693
- let sessionId = 'default';
694
- // Check if desktopData has nested structure
695
- if (desktopData && typeof desktopData === 'object') {
696
- if (desktopData.data && typeof desktopData.data === 'object') {
697
- // Nested structure: data.data.session_id
698
- sessionId = desktopData.data.session_id || sessionId;
699
- }
700
- // Also check flat structure
701
- if (!sessionId || sessionId === 'default') {
702
- sessionId = desktopData.session_id || sessionId;
703
- }
704
- }
705
- // Return credential in expected format for subsequent API calls
706
- return {
707
- [sessionId]: result.credential
708
- };
704
+ const handler = new DesktopHandler();
705
+ // Extract QR code data - convert to QRCodeData format for modal
706
+ const qrCodeData = {
707
+ iosQRCode: ((_c = desktopData.data) === null || _c === void 0 ? void 0 : _c.ios_qr_image) || desktopData.ios_qr_image ||
708
+ ((_d = desktopData.data) === null || _d === void 0 ? void 0 : _d.qr_code_image) || desktopData.qr_code_image || desktopData.qr_code || '',
709
+ androidQRCode: ((_e = desktopData.data) === null || _e === void 0 ? void 0 : _e.android_qr_image) || desktopData.android_qr_image,
710
+ iosUrl: ((_f = desktopData.data) === null || _f === void 0 ? void 0 : _f.ios_url) || desktopData.ios_url,
711
+ androidUrl: ((_g = desktopData.data) === null || _g === void 0 ? void 0 : _g.android_url) || desktopData.android_url
712
+ };
713
+ // Also keep snake_case format for extended response
714
+ const qrCodeDataSnakeCase = {
715
+ ios_qr_image: ((_h = desktopData.data) === null || _h === void 0 ? void 0 : _h.ios_qr_image) || desktopData.ios_qr_image,
716
+ android_qr_image: ((_j = desktopData.data) === null || _j === void 0 ? void 0 : _j.android_qr_image) || desktopData.android_qr_image,
717
+ qr_code: ((_k = desktopData.data) === null || _k === void 0 ? void 0 : _k.qr_code_image) || desktopData.qr_code_image || desktopData.qr_code,
718
+ challenge: (_l = desktopData.data) === null || _l === void 0 ? void 0 : _l.challenge
719
+ };
720
+ // Polling options
721
+ const pollingEndpointToUse = (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.pollingEndpoint) ||
722
+ (desktopOptions === null || desktopOptions === void 0 ? void 0 : desktopOptions.pollingEndpoint) ||
723
+ ((_m = this.config.endpoints) === null || _m === void 0 ? void 0 : _m.polling);
724
+ const pollingOptions = Object.assign(Object.assign({}, desktopOptions), { pollingEndpoint: pollingEndpointToUse, pollingInterval: (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.pollingInterval) || (desktopOptions === null || desktopOptions === void 0 ? void 0 : desktopOptions.pollingInterval) || this.config.pollingInterval || 2000, maxPollingAttempts: (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.maxPollingAttempts) || (desktopOptions === null || desktopOptions === void 0 ? void 0 : desktopOptions.maxPollingAttempts) || this.config.maxPollingAttempts || 150, onQRCodeReady: undefined, onStatusUpdate: undefined });
725
+ // Decide whether to show modal based on preventDefaultUI
726
+ const showModal = !preventDefaultUI;
727
+ let modal;
728
+ let modalRef = undefined;
729
+ console.log('[Desktop] Modal decision:', {
730
+ preventDefaultUI,
731
+ showModal,
732
+ executionMode,
733
+ hasInvokeOptions: !!invokeOptions,
734
+ hasDesktopOptions: !!desktopOptions
735
+ });
736
+ if (showModal) {
737
+ console.log('[Desktop] Creating modal with QR data:', qrCodeData);
738
+ // Create and setup modal
739
+ modal = new AuthModal(invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.modalOptions, invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.callbacks);
740
+ modal.setCloseCallback(() => {
741
+ this.log('Desktop QR modal closed by user, cancelling polling');
742
+ handler.cancel();
743
+ });
744
+ // Add UI callbacks to polling options
745
+ pollingOptions.onQRCodeReady = (qrData) => {
746
+ console.log('[Desktop] onQRCodeReady callback triggered:', qrData);
747
+ modal.showQRCode(qrData, 'Scan with your mobile device');
748
+ };
749
+ pollingOptions.onStatusUpdate = (status) => {
750
+ if (status.status === 'pending') {
751
+ modal.updateStatus('Waiting for authentication...');
752
+ }
753
+ else if (status.status === 'authenticated') {
754
+ modal.updateStatus('Authentication successful!');
755
+ setTimeout(() => modal.close(), 1500);
709
756
  }
710
- else {
711
- throw this.createError(PhoneAuthErrorCode.USER_DENIED, result.error || 'Desktop authentication failed');
757
+ else if (status.status === 'expired') {
758
+ modal.updateStatus('QR code expired', true);
712
759
  }
760
+ else if (status.status === 'error') {
761
+ modal.updateStatus('Authentication failed', true);
762
+ }
763
+ };
764
+ // Note: We don't show the QR code here. It will be shown by the onQRCodeReady callback
765
+ // that gets triggered immediately when handler.invoke() is called
766
+ modalRef = modal;
767
+ }
768
+ else {
769
+ console.log('[Desktop] Modal not shown - preventDefaultUI is true');
770
+ }
771
+ // Create credential promise
772
+ const startPolling = () => handler.invoke(plainResponse, pollingOptions).then(result => {
773
+ if (result.authenticated && result.credential) {
774
+ return {
775
+ credential: result.credential,
776
+ session: plainResponse.session,
777
+ authenticated: true
778
+ };
779
+ }
780
+ else {
781
+ throw this.createError(PhoneAuthErrorCode.USER_DENIED, result.error || 'Desktop authentication failed');
782
+ }
783
+ });
784
+ // Handle based on execution mode
785
+ if (executionMode === 'extended') {
786
+ // Extended mode - return control methods
787
+ const credentialPromise = showModal ? startPolling() : Promise.resolve({
788
+ credential: '',
789
+ session: plainResponse.session,
790
+ authenticated: false
713
791
  });
714
792
  return {
715
- strategy: API.AUTHENTICATION_STRATEGY.DESKTOP,
716
- qrCode: (_b = desktopData.data) === null || _b === void 0 ? void 0 : _b.qr_code_image, // data.data.qr_code_image
717
- pollingPromise,
718
- session: plainResponse.session
793
+ strategy: 'desktop',
794
+ session: plainResponse.session,
795
+ credential: credentialPromise,
796
+ qr_code_data: qrCodeDataSnakeCase,
797
+ modal_ref: modalRef,
798
+ start_polling: startPolling,
799
+ stop_polling: () => {
800
+ handler.cleanup();
801
+ if (modal)
802
+ modal.close();
803
+ },
804
+ cancel: () => {
805
+ handler.cancel();
806
+ handler.cleanup();
807
+ if (modal)
808
+ modal.close();
809
+ },
810
+ is_polling: showModal // If modal shown, polling already started
719
811
  };
720
812
  }
721
813
  else {
722
- // UI mode - show QR code modal
723
- const handler = new DesktopHandler();
724
- const modal = new AuthModal(invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.modalOptions, invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.callbacks);
725
- // Default options with modal display
726
- const options = Object.assign(Object.assign({}, desktopOptions), {
727
- // Don't pass config.endpoints.polling - let backend-provided status_url take priority
728
- // Only explicit desktopOptions.pollingEndpoint will override backend URL
729
- 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) => {
730
- // Show QR code in modal (supports both single and dual-platform)
731
- modal.showQRCode(qrCodeData, 'Scan with your mobile device');
732
- }, onStatusUpdate: (status) => {
733
- // Update status in modal
734
- if (status.status === 'pending') {
735
- modal.updateStatus('Waiting for authentication...');
736
- }
737
- else if (status.status === 'authenticated') {
738
- modal.updateStatus('Authentication successful!');
739
- setTimeout(() => modal.close(), 1500);
740
- }
741
- else if (status.status === 'expired') {
742
- modal.updateStatus('QR code expired', true);
743
- }
744
- else if (status.status === 'error') {
745
- modal.updateStatus('Authentication failed', true);
746
- }
747
- } });
814
+ // Standard mode - return credential when complete
815
+ // Start polling and wait for result
748
816
  try {
749
- const result = yield handler.invoke(plainResponse, options);
750
- if (result.authenticated && result.credential) {
751
- // Extract session ID from nested or flat structure
752
- let sessionId = 'default';
753
- // Check if desktopData has nested structure
754
- if (desktopData && typeof desktopData === 'object') {
755
- if (desktopData.data && typeof desktopData.data === 'object') {
756
- // Nested structure: data.data.session_id
757
- sessionId = desktopData.data.session_id || sessionId;
758
- }
759
- // Also check flat structure
760
- if (!sessionId || sessionId === 'default') {
761
- sessionId = desktopData.session_id || sessionId;
762
- }
817
+ const credential = yield startPolling();
818
+ // Extract session ID for compatibility
819
+ let sessionId = 'default';
820
+ if (desktopData && typeof desktopData === 'object') {
821
+ if (desktopData.data && typeof desktopData.data === 'object') {
822
+ sessionId = desktopData.data.session_id || sessionId;
823
+ }
824
+ if (!sessionId || sessionId === 'default') {
825
+ sessionId = desktopData.session_id || sessionId;
763
826
  }
764
- // Return credential in expected format for subsequent API calls
765
- return {
766
- [sessionId]: result.credential
767
- };
768
- }
769
- else {
770
- throw this.createError(PhoneAuthErrorCode.USER_DENIED, result.error || 'Desktop authentication failed');
771
- }
772
- }
773
- catch (error) {
774
- if (error instanceof Error && error.name === 'PhoneAuthError') {
775
- throw error;
776
827
  }
777
- throw this.createError(PhoneAuthErrorCode.INTERNAL_SERVER_ERROR, `Desktop authentication error: ${error instanceof Error ? error.message : 'Unknown error'}`, { originalError: error });
828
+ return { [sessionId]: credential.credential };
778
829
  }
779
830
  finally {
780
- // Ensure handler cleanup
781
831
  handler.cleanup();
782
- modal.close();
832
+ if (modal)
833
+ modal.close();
783
834
  }
784
835
  }
785
836
  }
786
837
  else if (plainResponse.authentication_strategy === API.AUTHENTICATION_STRATEGY.LINK) {
787
838
  // Link strategy - app-based authentication (iOS/Android)
788
839
  const linkData = plainResponse.data;
789
- // Check if headless mode is requested
790
- if (isHeadless) {
791
- // Return raw data for custom UI implementation
792
- const handler = new LinkHandler();
793
- // Create reusable trigger function that ONLY opens the App Clip
794
- // This can be called multiple times without restarting polling
795
- const triggerLink = () => {
796
- var _a, _b;
797
- try {
798
- window.open(linkData.url, '_blank');
799
- (_a = invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.onTriggerAttempt) === null || _a === void 0 ? void 0 : _a.call(invokeOptions, {
800
- strategy: API.AUTHENTICATION_STRATEGY.LINK,
801
- url: linkData.url,
802
- success: true
803
- });
804
- }
805
- catch (error) {
806
- (_b = invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.onTriggerAttempt) === null || _b === void 0 ? void 0 : _b.call(invokeOptions, {
807
- strategy: API.AUTHENTICATION_STRATEGY.LINK,
808
- url: linkData.url,
809
- success: false,
810
- error
811
- });
812
- }
813
- };
814
- // Auto-trigger by default for Link strategy
815
- // This opens the App Clip immediately while preserving user gesture context
816
- if ((invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.autoTrigger) !== false) {
817
- triggerLink();
840
+ const handler = new LinkHandler();
841
+ // Create reusable trigger function that ONLY opens the App Clip
842
+ const triggerLink = () => {
843
+ var _a, _b;
844
+ try {
845
+ window.open(linkData.url, '_blank');
846
+ (_a = invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.onTriggerAttempt) === null || _a === void 0 ? void 0 : _a.call(invokeOptions, {
847
+ strategy: API.AUTHENTICATION_STRATEGY.LINK,
848
+ url: linkData.url,
849
+ success: true
850
+ });
818
851
  }
819
- // Start polling in the background (independent of trigger)
820
- const pollingPromise = new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
821
- var _a;
822
- try {
823
- const result = yield handler.invoke(plainResponse, {
824
- pollingEndpoint: (_a = this.config.endpoints) === null || _a === void 0 ? void 0 : _a.polling,
825
- pollingInterval: this.config.pollingInterval || 2000,
826
- maxPollingAttempts: this.config.maxPollingAttempts || 150,
827
- // openMethod removed - always uses window.open()
828
- onLinkOpened: undefined,
829
- onStatusUpdate: undefined
830
- });
831
- if (result.authenticated && result.credential) {
832
- // Return credential in the expected format
833
- const aggregatorId = this.config.aggregatorId || 'default';
834
- resolve({ [aggregatorId]: result.credential });
835
- }
836
- else {
837
- reject(this.createError(PhoneAuthErrorCode.USER_DENIED, result.error || 'Link authentication failed'));
838
- }
839
- }
840
- catch (error) {
841
- reject(error);
842
- }
843
- }));
852
+ catch (error) {
853
+ (_b = invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.onTriggerAttempt) === null || _b === void 0 ? void 0 : _b.call(invokeOptions, {
854
+ strategy: API.AUTHENTICATION_STRATEGY.LINK,
855
+ url: linkData.url,
856
+ success: false,
857
+ error
858
+ });
859
+ }
860
+ };
861
+ // Link always auto-opens the app (no SDK UI by default)
862
+ // Open immediately to preserve user gesture context
863
+ if ((invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.autoTrigger) !== false) {
864
+ triggerLink();
865
+ }
866
+ // Start polling in the background
867
+ console.log('[PhoneAuth Client] Link polling config:', {
868
+ fromInvokeOptions: invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.pollingEndpoint,
869
+ fromClientConfig: (_o = this.config.endpoints) === null || _o === void 0 ? void 0 : _o.polling,
870
+ finalPollingEndpoint: (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.pollingEndpoint) || ((_p = this.config.endpoints) === null || _p === void 0 ? void 0 : _p.polling)
871
+ });
872
+ const pollingOptions = {
873
+ pollingEndpoint: (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.pollingEndpoint) || ((_q = this.config.endpoints) === null || _q === void 0 ? void 0 : _q.polling),
874
+ pollingInterval: (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.pollingInterval) || this.config.pollingInterval || 2000,
875
+ maxPollingAttempts: (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.maxPollingAttempts) || this.config.maxPollingAttempts || 150,
876
+ onLinkOpened: undefined,
877
+ onStatusUpdate: undefined
878
+ };
879
+ console.log('[PhoneAuth Client] Final Link polling options:', pollingOptions);
880
+ // Create credential promise
881
+ const credentialPromise = handler.invoke(plainResponse, pollingOptions).then(result => {
882
+ if (result.authenticated && result.credential) {
883
+ return {
884
+ credential: result.credential,
885
+ session: plainResponse.session,
886
+ authenticated: true
887
+ };
888
+ }
889
+ else {
890
+ throw this.createError(PhoneAuthErrorCode.USER_DENIED, result.error || 'Link authentication failed');
891
+ }
892
+ });
893
+ // Handle based on execution mode
894
+ if (executionMode === 'extended') {
895
+ // Extended mode - return control methods
844
896
  return {
845
- strategy: API.AUTHENTICATION_STRATEGY.LINK,
846
- url: linkData.url,
847
- pollingPromise,
897
+ strategy: 'link',
848
898
  session: plainResponse.session,
849
- // Trigger function to open App Clip (can be called multiple times)
850
- // Does NOT restart polling - just opens the URL
851
- trigger: triggerLink
852
- };
853
- }
854
- else {
855
- // UI mode - show button to open app link
856
- const modal = new AuthModal(invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.modalOptions, invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.callbacks);
857
- const handler = new LinkHandler();
858
- // Show link button in modal - user MUST click the button to open the app link
859
- // This is critical for iOS to recognize the app link (requires user interaction)
860
- yield modal.showLinkButton(linkData.url);
861
- // Link options - the app link will be opened by user clicking the modal button
862
- const options = {
863
- // Use configured polling endpoint if available
864
- pollingEndpoint: (_c = this.config.endpoints) === null || _c === void 0 ? void 0 : _c.polling,
865
- pollingInterval: this.config.pollingInterval || 2000, // Default 2 second interval
866
- maxPollingAttempts: this.config.maxPollingAttempts || 150, // Default 5 minutes total
867
- onLinkOpened: () => {
868
- this.log('Authentication app link opened');
869
- modal.updateStatus('Waiting for app authentication...');
899
+ credential: credentialPromise,
900
+ data: {
901
+ app_url: linkData.url
870
902
  },
871
- onStatusUpdate: (status) => {
872
- this.log('Link authentication status update:', status);
873
- if (status.status === 'authenticated') {
874
- modal.updateStatus('Authentication successful!');
875
- setTimeout(() => modal.close(), 1500);
876
- }
877
- else if (status.status === 'error') {
878
- modal.updateStatus('Authentication failed', true);
879
- }
903
+ // Re-open the app link
904
+ trigger: triggerLink,
905
+ // Polling control
906
+ start_polling: () => handler.invoke(plainResponse, pollingOptions).then(result => ({
907
+ credential: result.credential || '',
908
+ session: plainResponse.session,
909
+ authenticated: result.authenticated
910
+ })),
911
+ stop_polling: () => handler.cleanup(),
912
+ cancel: () => {
913
+ handler.cancel();
914
+ handler.cleanup();
880
915
  },
881
- onTimeout: () => {
882
- this.log('Link authentication timed out');
883
- modal.updateStatus('Authentication timed out', true);
884
- }
916
+ is_polling: true
885
917
  };
886
- try {
887
- const result = yield handler.invoke(plainResponse, options);
888
- if (result.authenticated && result.credential) {
889
- // Return the credential for further processing
890
- // The credential is typically the session key
891
- const aggregatorId = this.config.aggregatorId || 'default';
892
- modal.close();
893
- return { [aggregatorId]: result.credential };
894
- }
895
- else {
896
- throw this.createError(PhoneAuthErrorCode.VERIFICATION_FAILED, result.error || 'Link authentication failed');
897
- }
898
- }
899
- catch (error) {
900
- modal.close();
901
- if (error instanceof Error && error.name === 'PhoneAuthError') {
902
- throw error;
903
- }
904
- throw this.createError(PhoneAuthErrorCode.INTERNAL_SERVER_ERROR, `Link authentication error: ${error instanceof Error ? error.message : 'Unknown error'}`, { originalError: error });
905
- }
906
- finally {
907
- // Ensure handler cleanup
908
- handler.cleanup();
909
- }
918
+ }
919
+ else {
920
+ // Standard mode - return credential when complete
921
+ // Wait for credential and return in standard format
922
+ const credential = yield credentialPromise;
923
+ const aggregatorId = this.config.aggregatorId || 'default';
924
+ return { [aggregatorId]: credential.credential };
910
925
  }
911
926
  }
912
927
  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';